package eu.ahsx.limboa.media
{
	import eu.ahsx.limboa.utils.ArrayUtils;
	import eu.ahsx.limboa.utils.ObjectUtils;
	import eu.ahsx.limboa.utils.Token;
	
	import flash.events.TimerEvent;
	import flash.utils.Dictionary;
	import flash.utils.Timer;
	
	/**
	 * 	Manage the assets for the application
	 * 	Can deal with data (xml, txt, json), fonts, css, images, swf	
	 * 
	 * 	@author Masy Alexandre
	 * */
	internal final class MediaCore
	{
		/**
		 * 	Maximum of concurent process
		 * 	@private
		 * */
		private const MAXCONCURENTPROCESS:int = 2;
		
		/**
		 * 	Instance
		 * 	@private	
		 * */
		private static var _instance:MediaCore;
		
		/**
		 * 	Hight priority waiting items
		 * 	@private
		 * */
		private var _waitHight:Dictionary;
		
		/**
		 * 	Normal priority waiting items
		 * 	@private
		 * */
		private var _waitNormal:Dictionary;
		
		/**
		 * 	Low priority waiting items
		 * 	@private
		 * */
		private var _waitLow:Dictionary;
		
		/**
		 * 	Processing items
		 * 	@private
		 * */
		private var _processing:Dictionary
		
		/**
		 * 	Finished files
		 * 	@private
		 * */
		private var _refs:Dictionary;
		
		/**
		 * 	Updater
		 * 	@private
		 * */
		private var _updater:Timer;
		
		/**
		 * 	Check point to retrieve the reference to the core
		 * 
		 * 	@return Return a reference to the standard core
		 * 	@private
		 * 	@langversion ActionScript 3.0
		 * 	@playerversion Flash 9
		 * */
		internal static function getInstance():MediaCore
		{
			if (!_instance)
				_instance = new MediaCore(arguments.callee);
				
			return _instance;
		}
		
		/**
		 * 	Allow internal class to retrieve a new core reference.
		 * 	This may be handy to have a local core to handle priority on a list of files
		 * 	
		 * 	@return Return a new core instance
		 * 	@private
		 * 	@langversion ActionScript 3.0
		 * 	@playerversion Flash 9
		 * */
		internal static function getCore():MediaCore
		{
			return new MediaCore(arguments.callee);
		}
		
		/**
		 * 	Ask the loading of a file
		 * 
		 * 	@param	file AssetsFile to be loaded
		 * 	@param 	core AssetsCore	The core to use
		 * 	@return String	Uniq token used to identified the file
		 * 	@langversion ActionScript 3.0
		 * 	@playerversion Flash 9
		 * */
		internal static function load(file:MediaFile, core:MediaCore = null):Boolean
		{
			if (!core)
				core = getInstance();
			
			return core.add( file );
		}
		
		/**
		 * 	retrieve the content of a file related to the filetype
		 * 	
		 * 	@param	id String id of the file
		 * 	@return * the content of the file
		 * 	@throws 0 Error File does not exists
		 * 	@langversion ActionScript 3.0
		 * 	@playerversion Flash 9
		 * */
		internal static function getContent(file:MediaFile):*
		{
			var f:MediaFile = file;
			var ret:*;
			
			if (!f)
				throw new Error("File does not exists");
				
			switch( f.type )
			{
				case MediaFileType.CSS:
				case MediaFileType.DATA_JSON:
				case MediaFileType.DATA_TXT:
				case MediaFileType.DATA_XML:
				case MediaFileType.FONT:
				case MediaFileType.AVM1:
				case MediaFileType.AVM2:
				case MediaFileType.IMAGE:
					ret = f.loader.data;
					break;
				default:
					throw new Error("Filetype not found/defined");
			}	
				
			return ret;
		}
		
		/**
		 * 	Retrieve a file by its url
		 * 
		 * 	@param	url String
		 * 	@param 	core AssetsCore	The core to use
		 * 	@return AssetsFile
		 * 	@langversion ActionScript 3.0
		 * 	@playerversion Flash 9
		 * */
		internal static function getByURL(url:String, core:MediaCore = null):MediaFile
		{
			if (!core)
				core = getInstance();
			
			return core.getFileByURL(url);
		}
		
		/**
		 * 	Remove an event from list and prevent from being loaded
		 * 	
		 * 	@param	f AssetsFile	The file to remove
		 * 	@param 	core AssetsCore	The core to use
		 * 	@langversion ActionScript 3.0
		 * 	@playerversion Flash 9
		 * */
		internal static function remove(f:MediaFile, core:MediaCore = null):void
		{
			if (!core)
				core = getInstance();
			
			core.remove(f);	
		}
		
		/**
		 * 	Constructor
		 * 
		 * 	@param	caller 	Function that create the object. 
		 * 					As this class is used as a singleton, it as to
		 * 					be the getInstance() method
		 * 	@langversion ActionScript 3.0
		 * 	@playerversion Flash 9
		 * */
		public function MediaCore(caller:Function)
		{
			super();
			
			if (caller != MediaCore.getInstance && caller != MediaCore.getCore)
				throw new Error("Assets core can't be access directly");
			
			_refs = new Dictionary();
			_processing = new Dictionary();
			_waitHight = new Dictionary();
			_waitNormal = new Dictionary();
			_waitLow = new Dictionary();
			
			_updater = new Timer(1000);
			_updater.addEventListener(TimerEvent.TIMER, update);
			_updater.start();
		}
		
		/**
		 * 	Retrieve the AssetManagerFile associated to an id
		 * 	
		 * 	@param	fileid String
		 * 	@return AssetsFile
		 * 	@private
		 * 	@langversion ActionScript 3.0
		 * 	@playerversion Flash 9
		 * */
		private function getFileById(fileid:String):MediaFile
		{
			var ret:MediaFile;
			var f:MediaFile;
			
			// finished files
			if (_refs[fileid])
				return _refs[fileid];
			
			// processing file
			if (_processing[fileid])
				return _processing[fileid];
			
			// hight waiting list
			if (_waitHight[fileid])
				return _waitHight[fileid];
			
			// normal waiting list
			if (_waitNormal[fileid])
				return _waitNormal[fileid];
			
			// low waiting list
			if (_waitLow[fileid])
				return _waitLow[fileid];

			return ret;
		}
		
		/**
		 * 	Retrieve the AssetManagerFile associated to an url
		 * 	
		 * 	@param	url String
		 * 	@return AssetsFile
		 * 	@private
		 * 	@langversion ActionScript 3.0
		 * 	@playerversion Flash 9
		 * */
		private function getFileByURL(url:String):MediaFile
		{
			var ret:MediaFile;
			var f:MediaFile
			
			// look in the finished files
			for each (f in _refs)
			{
				if (f.url == url)
				{
					ret = f;
					return ret;
					break;
				}
			}
			
			// look in the processing files
			for each( f in _processing)
			{
				if (f.url == url)
				{
					ret = f;
					return ret;
					break;
				}	
			}
			
			// look in the hight waiting list
			for each(f in _waitHight)
			{
				if (f.url == url)
				{
					ret = f;
					return ret;
					break;
				}	
			}
			
			// look in the normal waiting list
			for each( f in _waitNormal)
			{
				if (f.url == url)
				{
					ret = f;
					return ret;
					break;
				}	
			}
			
			// look in the low waiting list
			for each( f in _waitLow)
			{
				if (f.url == url)
				{
					ret = f;
					return ret;
					break;
				}	
			}
			
			return ret;
		}
		
		/**
		 * 	Add a file to the right waiting list. 
		 * 	
		 * 	@param	file AssetsFile
		 * 	@return Boolean
		 * 	@private
		 * 	@langversion ActionScript 3.0
		 * 	@playerversion Flash 9
		 * */
		private function add(file:MediaFile):Boolean
		{
			if (exists(file))
			{
				var f:MediaFile = getFileByURL(file.url);
				if (isWaiting(f) || isProcessing(f) )
				{
					if (file.start.length > 0)
						ArrayUtils.merge(f.start, file.start);
					
					if (file.init.length > 0)
						ArrayUtils.merge(f.init, file.init);
					
					if (file.progress.length > 0)
						ArrayUtils.merge(f.progress, file.progress);
					
					if (file.complete.length > 0)
						ArrayUtils.merge(f.complete, file.complete);
					
					if (file.done.length > 0)
						ArrayUtils.merge(f.done, file.done);
					
					if (file.error.length > 0)
						ArrayUtils.merge(f.error, file.error);
					
					process();
					
					return true;
				}
				else
				{
					if (file.forced)
					{
						delete _processing[f.id];
						delete _refs[f.id];
					}
					else
						return false;
				}
			}
			
			file.id = Token.uniq(20);
			
			switch (file.priority)
			{
				case MediaPriority.HIGH:
					_waitHight[file.id] = file;
					break;
				case MediaPriority.LOW:
					_waitLow[file.id] = file;
					break;
				case MediaPriority.NORMAL: default:
					_waitNormal[file.id] = file;
					break;
			}
			
			process();
			
			return true;
		}
		
		/**
		 * 	Check if a file exist in the references
		 * 
		 * 	@param	file AssetsFile
		 * 	@return Boolean
		 * 	@private
		 * 	@langversion ActionScript 3.0
		 * 	@playerversion Flash 9
		 * */
		private function exists(file:MediaFile):Boolean
		{
			return getFileByURL(file.url) != null;
		}
		
		/**
		 * 	Check if a file is on a waiting list
		 * 
		 * 	@param	file AssetsFile
		 * 	@return Boolean
		 * 	@private
		 * 	@langversion ActionScript 3.0
		 * 	@playerversion Flash 9
		 * */
		private function isWaiting(file:MediaFile):Boolean
		{
			return _waitHight[file.id] || _waitLow[file.id] || _waitNormal[file.id];
		}
		
		/**
		 * 	Check if the file is on the processing list
		 * 
		 * 	@param	file AssetsFile
		 * 	@return Boolean
		 * 	@private
		 * 	@langversion ActionScript 3.0
		 * 	@playerversion Flash 9
		 * */
		private function isProcessing(file:MediaFile):Boolean
		{
			return _processing[file.id];
		}
		
		/**
		 * 	Procress the queue to load the desired file
		 * 	
		 * 	@private
		 * 	@langversion ActionScript 3.0
		 * 	@playerversion Flash 9
		 * */
		private function process():void
		{
			if ((ObjectUtils.getLength(_waitHight)== 0 && ObjectUtils.getLength(_waitLow)== 0 && ObjectUtils.getLength(_waitNormal)== 0) || ObjectUtils.getLength(_processing)>= MAXCONCURENTPROCESS)
				return;
			
			var nf:MediaFile = getNextFile();
			var l:IAssetsLoader;
			
			_processing[nf.id] = nf;

			switch (nf.type)
			{
				case MediaFileType.APPLICATION:
					break;
				case MediaFileType.CSS:
					l = new CSSLoader( MediaFile.debug );
					break;
				case MediaFileType.DATA_JSON:
				case MediaFileType.DATA_TXT:
					l = new DataLoader(MediaFile.debug);
					break;
				case MediaFileType.DATA_XML:
					l = new XMLLoader(MediaFile.debug);
					break;
					break;
				case MediaFileType.FONT:
					l = new FontLoader(MediaFile.debug);
					break;
				case MediaFileType.AVM1:
					l = new AVM1Loader(MediaFile.debug);
				case MediaFileType.AVM2:
					l = new AVM2Loader(MediaFile.debug);
					break;
				case MediaFileType.IMAGE:
					l = new ImageLoader(MediaFile.debug);
					break;
			}
			
			nf.loader = l;
			nf.loader.addEventListener(MediaEvent.INIT, loadInit, false, 0, true);
			nf.loader.addEventListener(MediaEvent.START, loadStart, false, 0, true);
			nf.loader.addEventListener(MediaEvent.PROGRESS, loadProgress, false, 0, true);
			nf.loader.addEventListener(MediaEvent.COMPLETE, loadComplete, false, 0, true);
			nf.loader.addEventListener(MediaEvent.DONE, loadDone, false, 0, true);
			nf.loader.addEventListener(MediaEvent.ERROR, loadError, false, 0, true);
			
			l.load(nf);
		}
		
		/**
		 * 	Called when a file download have been initiated
		 * 
		 * 	@param	event AssetsEvent
		 * 	@private
		 * 	@langversion ActionScript 3.0
		 * 	@playerversion Flash 9
		 * */
		private function loadInit(event:MediaEvent):void
		{
			var f:MediaFile = event.file;
			fireCallback(f.init, f);
		}
		
		/**
		 * 	Called when a file download start
		 * 
		 * 	@param	event AssetsEvent
		 * 	@private
		 * 	@langversion ActionScript 3.0
		 * 	@playerversion Flash 9
		 * */
		private function loadStart(event:MediaEvent):void
		{
			var f:MediaFile = event.file;
			fireCallback(f.start, f);
		}
		
		/**
		 * 	Called when the download progress
		 * 
		 * 	@param	event AssetsEvent
		 * 	@private
		 * 	@langversion ActionScript 3.0
		 * 	@playerversion Flash 9
		 * */
		private function loadProgress(event:MediaEvent):void
		{
			var f:MediaFile = event.file;
			fireCallback(f.progress, f);
		}
		
		/**
		 * 	Called when a file have been loaded
		 * 	
		 * 	@param	event AssetsEvent
		 * 	@private
		 * 	@langversion ActionScript 3.0
		 * 	@playerversion Flash 9
		 * */
		private function loadComplete(event:MediaEvent):void
		{
			var f:MediaFile = event.file;
			
			if (f != null)
			{
				delete _processing[f.id];
				_refs[f.id] = f;
				fireCallback(f.complete, f);
			}
		}
		
		/**
		 * 	Called when en error occured while loading a file
		 * 	
		 * 	@param	event AssetsEvent
		 * 	@private
		 * 	@langversion ActionScript 3.0
		 * 	@playerversion Flash 9
		 * */
		private function loadError(event:MediaEvent):void
		{
			var f:MediaFile = event.file;
			
			if (f != null)
			{
				delete _processing[f.id];
				fireCallback(f.error, f);
			}
		}
		
		/**
		 * 	Called when the donwload have been completed
		 * 
		 * 	@param	event AssetsEvent 
		 * 	@private
		 * 	@langversion ActionScript 3.0
		 * 	@playerversion Flash 9
		 * */
		private function loadDone(event:MediaEvent):void
		{
			var f:MediaFile = event.file;
			f.loader.removeEventListener(MediaEvent.INIT, loadInit);
			f.loader.removeEventListener(MediaEvent.START, loadStart);
			f.loader.removeEventListener(MediaEvent.PROGRESS, loadProgress);
			f.loader.removeEventListener(MediaEvent.COMPLETE, loadComplete);
			f.loader.removeEventListener(MediaEvent.DONE, loadDone);
			f.loader.removeEventListener(MediaEvent.ERROR, loadError);
			
			fireCallback(f.done, f);
			
			if (!_updater.running)
				_updater.start();
			process();
		}
		
		/**
		 * 	Call all the callback 
		 * 
		 * 	@param	list Vector.<Function>
		 * 	@param	file AssetsFile
		 * 	@private
		 * 	@langversion ActionScript 3.0
		 * 	@playerversion Flash 9
		 * */
		private function fireCallback(list:Array, file:MediaFile):void
		{
			var n:int = list.length;
			while(n--)
				list[n](file);
		}
		
		/**
		 * 	Choose the next file to be loaded
		 * 
		 * 	@return AssetsFile
		 * 	@private
		 * 	@langversion ActionScript 3.0
		 * 	@playerversion Flash 9
		 * */
		private function getNextFile():MediaFile
		{
			if (ObjectUtils.getLength(_waitHight)> 0)
				return ObjectUtils.getFirst(_waitHight, true);
			
			if (ObjectUtils.getLength(_waitNormal)> 0)
				return ObjectUtils.getFirst(_waitNormal, true);
				
			if (ObjectUtils.getLength(_waitLow)> 0)
				return ObjectUtils.getFirst(_waitLow, true);
			
			return null;
		}
		
		/**
		 * 	Remove a file from waiting list. 
		 * 	If the file is currently loading, it will be stopped.
		 * 	If the file is finished then we delete the reference to this one.
		 * 
		 * 	@param	f AssetsFile
		 * 	@private
		 * 	@langversion ActionScript 3.0
		 * 	@playerversion Flash 9
		 * */
		private function remove(f:MediaFile):void
		{
			// remove finished
			if ( _refs[f.id] )
			{
				delete _refs[f.id];
				f.loader.unload();
			}
			
			// remove processing
			if ( _processing[f.id] )
			{
				f.loader.removeEventListener(MediaEvent.INIT, loadInit);
				f.loader.removeEventListener(MediaEvent.START, loadStart);
				f.loader.removeEventListener(MediaEvent.PROGRESS, loadProgress);
				f.loader.removeEventListener(MediaEvent.COMPLETE, loadComplete);
				f.loader.removeEventListener(MediaEvent.DONE, loadDone);
				f.loader.removeEventListener(MediaEvent.ERROR, loadError);
				
				f.loader.stop();
				
				delete _processing[f.id];
				
				process();
			}
			
			// remove waiting
			if (_waitHight[f.id])
				delete _waitHight[f.id];
			
			if (_waitNormal[f.id])
				delete _waitNormal[f.id];
			
			if (_waitLow[f.id])
				delete _waitLow[f.id];
			
			// stop updater if not necessary
			if (_refs.length == 0)
				_updater.stop();
			
			// clean file
			f.start = null;
			f.init = null;
			f.progress = null;
			f.complete = null;
			f.done = null;
			f.error = null;
			f.loader = null;
			f.info = null;
			
		}
		
		/**
		 * 	Update the core, by incrementing the file kept by 1.
		 * 	If a file reach the max time, then it will be released
		 * 	
		 * 	@param event TimerEvent	
		 * 	@private
		 * 	@langversion ActionScript 3.0
		 * 	@playerversion Flash 9
		 * */
		private function update(event:TimerEvent):void
		{
			var n:int = this._refs.length;
			if (n > 0)
			{
				var f:MediaFile;
				while(n--)
				{
					f = _refs[n];
					if (f == null)
						continue;
					
					f.time++;
					
					if (f.time >= f.maxtime && f.maxtime != -1)
						f.remove();
				}
			}
		}
	}
}